package com.webex.ta.hydra.core.processor;

import com.webex.ta.hydra.core.AbstractProcessor;
import com.webex.ta.hydra.core.ParamDescriptor;
import com.webex.ta.hydra.core.TestResult;
import com.webex.ta.hydra.core.Command;
import com.webex.ta.hydra.core.engine.HydraContextService;
import com.webex.ta.hydra.core.engine.HydraVariables;
import com.webex.ta.hydra.util.XPathUtil;
import org.apache.xpath.XPathAPI;
import org.apache.xpath.objects.XObject;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;

/**
 * Created by Cisco WebEx.
 * User: vegaz
 * Date: 2010-9-9
 * Time: 10:16:07
 */
public class XPathExtractor extends AbstractProcessor {
    private static ParamDescriptor[] desc = new ParamDescriptor[3];

    static {
        desc[0] = new ParamDescriptor("XPath");
        desc[1] = new ParamDescriptor("variable Name");
        desc[2] = new ParamDescriptor("default value", true);
    }

    private static final String MATCH_NR = "matchNr";

    public void process(String... args) {
        String xpath = params[0].getStringValue();
        String refName = params[1].getStringValue();
        String defaultValue = null;
        if (params.length >= 3) {
            defaultValue = params[2].getStringValue();
        }

        HydraVariables vars = HydraContextService.getContext().getVariables();
        if (defaultValue != null)
            vars.put(refName, defaultValue);
        vars.put(concat(refName, MATCH_NR), "0"); // In case parse fails // $NON-NLS-1$
        vars.remove(concat(refName, "1"));
        try {
            Document d = parseResponse(HydraContextService.getContext().getPreviousResult());
            getValuesForXPath(d, xpath, refName, vars);
        } catch (IOException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } catch (ParserConfigurationException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } catch (SAXException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } catch (TransformerException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }

    }

    public ParamDescriptor[] getParamDescriptors(Command cmd) {
        return desc;
    }

    private void getValuesForXPath(Document d, String query, String refName, HydraVariables vars) throws TransformerException {
        String val = null;
        XObject xObject = XPathAPI.eval(d, query);
        if (xObject.getType() == XObject.CLASS_NODESET) {
            NodeList matches = xObject.nodelist();
            int length = matches.getLength();
            vars.put(concat(refName, MATCH_NR), String.valueOf(length));
            for (int i = 0; i < length; i++) {
                Node match = matches.item(i);
                if (match instanceof Element) {
                    // elements have empty nodeValue, but we are usually interested in their content
                    final Node firstChild = match.getFirstChild();
                    if (firstChild != null) {
//					   val = firstChild.getNodeValue();
                        val = getNodeContent(match, match.getNodeName(), false);
                    } else {
                        val = match.getNodeValue(); // TODO is this correct?
                    }
                } else {
                    val = match.getNodeValue();
                }
                if (val != null) {
                    if (i == 0) {// Treat 1st match specially
                        vars.put(refName, val);
                    }
                    vars.put(concat(refName, String.valueOf(i + 1)), val);
                }
            }

            vars.remove(concat(refName, String.valueOf(length + 1)));
        } else {
            val = xObject.toString();
            vars.put(concat(refName, MATCH_NR), "1");
            vars.put(refName, val);
            vars.put(concat(refName, "1"), val);
            vars.remove(concat(refName, "2"));
        }
    }

    private String concat(String s1, String s2) {
        return new StringBuffer(s1).append("_").append(s2).toString(); // $NON-NLS-1$
    }

    private String getNodeContent(Node node, String parentName, boolean takeParentNameIn) {
        if (node instanceof Element) {
            NodeList list = node.getChildNodes();
            String prefix, suffix;
            if (list != null && list.getLength() > 0) {
                String ret = "";
                for (int i = 0; i < list.getLength(); i++) {
                    ret = ret + getNodeContent(list.item(i), list.item(i).getNodeName(), true);
                }
                prefix = "<" + parentName + ">";
                suffix = "</" + parentName + ">";
                if (takeParentNameIn) {
                    return prefix + ret + suffix;
                } else {
                    return ret;
                }
            } else {
                prefix = "<" + parentName + ">";
                suffix = "</" + parentName + ">";
                return prefix + suffix; //node.getNodeValue();
            }
        } else {
            return node.getNodeValue();
        }
    }

    protected Document parseResponse(TestResult result)
      throws UnsupportedEncodingException, IOException, ParserConfigurationException, SAXException
    {
      String unicodeData = result.getResponse();
      // convert unicode String -> UTF-8 bytes
      byte[] utf8data = unicodeData.getBytes("UTF-8"); // $NON-NLS-1$
      ByteArrayInputStream in = new ByteArrayInputStream(utf8data);
      // this method assumes UTF-8 input data
      return XPathUtil.makeDocument(in,false,false, false,false);
    }
}
